import { getAmmoDefinition } from '../data/WeaponRepository';
import { EquipmentSlot, ItemBase, InventoryCategory } from './ItemTypes';
import { GlobalEventBus } from '../core/EventBus';

interface InventoryOptions {
  capacity: number;
}

export class Inventory {
  private slots: ItemBase[] = [];
  private bagCapacity: number;
  private equipment: Partial<Record<EquipmentSlot, ItemBase>> = {};
  private activeWeapon: EquipmentSlot = EquipmentSlot.Primary;

  constructor(options: InventoryOptions = { capacity: 30 }) {
    this.bagCapacity = options.capacity;
  }

  getBagItems(): ItemBase[] {
    return [...this.slots];
  }

  getEquipment(): Partial<Record<EquipmentSlot, ItemBase>> {
    return { ...this.equipment };
  }

  getActiveWeapon(): ItemBase | undefined {
    return this.equipment[this.activeWeapon];
  }

  switchWeapon(slot: EquipmentSlot.Primary | EquipmentSlot.Secondary) {
    this.activeWeapon = slot;
  }

  equip(slot: EquipmentSlot, item: ItemBase): boolean {
    if (!this.canEquip(slot, item.category)) {
      return false;
    }
    this.equipment[slot] = { ...item };
    return true;
  }

  unequip(slot: EquipmentSlot): ItemBase | undefined {
    const existing = this.equipment[slot];
    if (existing) {
      if (this.addItem(existing) === 0) {
        delete this.equipment[slot];
        return existing;
      }
    }
    return undefined;
  }

  addItem(item: ItemBase): number {
    let remaining = item.quantity;
    const maxStack = item.maxStack ?? this.getStackLimit(item);

    // try merge existing stacks
    for (const slot of this.slots) {
      if (remaining <= 0) {
        return 0;
      }
      if (this.canStack(slot, item)) {
        const space = (slot.maxStack ?? maxStack) - slot.quantity;
        if (space <= 0) {
          continue;
        }
        const transfer = Math.min(space, remaining);
        slot.quantity += transfer;
        remaining -= transfer;
      }
    }

    // add new stack
    while (remaining > 0 && this.slots.length < this.bagCapacity) {
      const stackQty = Math.min(maxStack, remaining);
      this.slots.push({ ...item, quantity: stackQty, maxStack });
      remaining -= stackQty;
    }

    // Emit ammo changed when ammo type items are added
    if (item.category === 'ammo') {
      this.emitAmmoChangedForActiveWeapon();
    }

    return remaining;
  }

  removeItemById(id: string, quantity = 1): boolean {
    return this.consumeItem((it) => it.id === id, quantity);
  }

  getAmmo(ammoType: string): number {
    let total = 0;
    for (const it of this.slots) {
      if (it.category === 'ammo' && it.ammoType === ammoType) {
        total += it.quantity;
      }
    }
    return total;
  }

  consumeItem(predicate: (item: ItemBase) => boolean, quantity = 1): boolean {
    for (let i = 0; i < this.slots.length; i++) {
      const slot = this.slots[i];
      if (!predicate(slot)) {
        continue;
      }
      if (slot.quantity > quantity) {
        slot.quantity -= quantity;
        return true;
      }
      if (slot.quantity === quantity) {
        this.slots.splice(i, 1);
        return true;
      }
      quantity -= slot.quantity;
      this.slots.splice(i, 1);
      i--;
      if (quantity <= 0) {
        return true;
      }
    }
    return quantity <= 0;
  }

  private canEquip(slot: EquipmentSlot, category: InventoryCategory): boolean {
    switch (slot) {
      case EquipmentSlot.Primary:
      case EquipmentSlot.Secondary:
        return category === 'weapon';
      case EquipmentSlot.Head:
      case EquipmentSlot.Chest:
      case EquipmentSlot.Hands:
      case EquipmentSlot.Feet:
        return category === 'gear';
      case EquipmentSlot.Ring1:
      case EquipmentSlot.Ring2:
        return category === 'ring';
      case EquipmentSlot.Charm:
        return category === 'charm';
      default:
        return false;
    }
  }

  private canStack(a: ItemBase, b: ItemBase): boolean {
    if (a.category !== b.category) {
      return false;
    }
    if (a.category === 'ammo') {
      return a.ammoType === b.ammoType;
    }
    return a.id === b.id;
  }

  private getStackLimit(item: ItemBase): number {
    if (item.category === 'ammo' && item.ammoType) {
      const def = getAmmoDefinition(item.ammoType as any);
      if (def) {
        return def.stack;
      }
    }
    return item.maxStack ?? 10;
  }

  private emitAmmoChangedForActiveWeapon() {
    const active = this.getActiveWeapon();
    const weaponId = active?.id ?? 'unknown';
    // Inventory does not manage magazine; emit reserve estimate only
    const reserve = this.slots
      .filter((it) => it.category === 'ammo')
      .reduce((sum, it) => sum + it.quantity, 0);
    GlobalEventBus.emit('ammo/changed', { weaponId, mag: 0, reserve });
  }
}
